package org.xx.armory.spring5.mvc;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpClientErrorException;
import org.xx.armory.commons.Converter;
import org.xx.armory.commons.StringConverter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.InvalidClassException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCause;
import static org.xx.armory.commons.ArrayUtils.truncate;
import static org.xx.armory.commons.StringConverter.parseInteger;
import static org.xx.armory.commons.StringConverter.parseLong;
import static org.xx.armory.commons.SysUtils.copyDate;
import static org.xx.armory.commons.Validators.rejectIfBlank;
import static org.xx.armory.commons.Validators.rejectIfNull;

public final class WebUtils {
    private static final String SESSION_ARRAY_ATTRIBUTE_DELIMITER = ",";

    private WebUtils() {
        throw new AssertionError();
    }

    public static String getFullPath(
            HttpServletRequest request
    ) {
        var path = request.getPathInfo();
        if (path == null) {
            path = request.getServletPath();
        }
        final var queryString = request.getQueryString();
        return isBlank(queryString) ? path : path + "?" + queryString;
    }

    public static void throwHttpClientException(
            HttpStatus statusCode
    ) {
        throw HttpClientErrorException.create(statusCode, "", HttpHeaders.EMPTY, null, null);
    }

    public static <E> List<E> getListAttribute(
            HttpSession session,
            String attributeName,
            Class<E> clazz
    ) {
        rejectIfNull(session, "session");
        rejectIfNull(clazz, "clazz");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        try {
            final var attr = session.getAttribute(attributeName);
            final var result = new ArrayList<E>();
            if (attr instanceof Collection<?>) {
                ((Collection<?>) attr).forEach(item -> {
                    try {
                        final var newItem = clazz.cast(item);
                        result.add(newItem);
                    } catch (ClassCastException ignored) {
                    }
                });
            }
            return unmodifiableList(result);
        } catch (RuntimeException ex) {
            return handleDeserializationException(ex, emptyList());
        }
    }

    public static String getStringAttribute(
            HttpSession session,
            String attributeName
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        try {
            final var attr = session.getAttribute(attributeName);
            return attr != null ? attr.toString() : "";
        } catch (RuntimeException ex) {
            return handleDeserializationException(ex, "");
        }
    }

    public static int getIntegerAttribute(
            HttpSession session,
            String attributeName,
            int defaultValue
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        try {
            final var attr = session.getAttribute(attributeName);
            if (attr == null) {
                return defaultValue;
            } else if (attr instanceof Number) {
                return ((Number) attr).intValue();
            } else {
                return parseInteger(attr.toString(), defaultValue);
            }
        } catch (RuntimeException ex) {
            return handleDeserializationException(ex, defaultValue);
        }
    }

    public static long getLongAttribute(
            HttpSession session,
            String attributeName,
            long defaultValue
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        try {
            final var attr = session.getAttribute(attributeName);
            if (attr == null) {
                return defaultValue;
            } else if (attr instanceof Number) {
                return ((Number) attr).longValue();
            } else {
                return parseLong(attr.toString(), defaultValue);
            }
        } catch (RuntimeException ex) {
            return handleDeserializationException(ex, defaultValue);
        }
    }

    public static BigDecimal getBigDecimalAttribute(
            HttpSession session,
            String attributeName,
            BigDecimal defaultValue
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        try {
            final var attr = session.getAttribute(attributeName);
            if (attr == null) {
                return defaultValue;
            } else if (attr instanceof Number) {
                return Converter.toBigDecimal(attr);
            } else {
                return StringConverter.parseBigDecimal(attr.toString(), defaultValue);
            }
        } catch (RuntimeException ex) {
            return handleDeserializationException(ex, defaultValue);
        }
    }

    public static Date getDateAttribute(
            HttpSession session,
            String attributeName,
            Date defaultValue
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        try {
            final var attr = session.getAttribute(attributeName);
            if (attr == null) {
                return defaultValue;
            } else if (attr instanceof Date) {
                return copyDate((Date) attr);
            } else {
                return StringConverter.parseDate(attr.toString(), defaultValue);
            }
        } catch (RuntimeException ex) {
            return handleDeserializationException(ex, defaultValue);
        }
    }

    public static void setArrayAttribute(
            HttpSession session,
            String attributeName,
            Object... array
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        if (array == null || Array.getLength(array) == 0) {
            session.removeAttribute(attributeName);
        } else {
            final var aLength = Array.getLength(array);
            session.setAttribute(attributeName, String.join(SESSION_ARRAY_ATTRIBUTE_DELIMITER, () -> new Iterator<>() {
                private int i = 0;

                @Override
                public boolean hasNext() {
                    return i < aLength;
                }

                @Override
                public CharSequence next() {
                    final var s = Array.get(array, i++);
                    return s != null ? s.toString() : "";
                }
            }));
        }
    }

    private static String[] getStringArrayAttribute(
            HttpSession session,
            String attributeName
    ) {
        final var attr = session.getAttribute(attributeName);

        return attr != null ? split(attr.toString(), SESSION_ARRAY_ATTRIBUTE_DELIMITER) : new String[0];
    }

    public static String[] getStringArrayAttribute(
            HttpSession session,
            String attributeName,
            int size
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        final var ss = getStringArrayAttribute(session, attributeName);
        return truncate(ss, size);
    }

    public static int[] getIntegerArrayAttribute(
            HttpSession session,
            String attributeName,
            int size
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        final var ss = stream(getStringArrayAttribute(session, attributeName))
                .filter(s -> s != null && !s.isBlank()).mapToInt(s -> parseInteger(s, 0)).toArray();
        return truncate(ss, size);
    }

    public static long[] getLongArrayAttribute(
            HttpSession session,
            String attributeName,
            int size
    ) {
        rejectIfNull(session, "session");
        attributeName = rejectIfBlank(attributeName, "attributeName").strip();

        final var ss = stream(getStringArrayAttribute(session, attributeName))
                .filter(s -> s != null && !s.isBlank()).mapToLong(s -> parseLong(s, 0L)).toArray();
        return truncate(ss, size);
    }

    /**
     * 处理反序列化时的错误。
     *
     * @param ex
     *         访问Session引发的原始异常。
     * @param defaultValue
     *         默认值。
     * @param <T>
     *         期望得到的反序列化类型。
     * @return 默认值。
     */
    private static <T> T handleDeserializationException(
            RuntimeException ex,
            T defaultValue
    ) {
        final var rex = getRootCause(ex);
        if (rex instanceof InvalidClassException) {
            return defaultValue;
        }

        throw ex;
    }
}
